<?php
if (!defined('PUN_ROOT')) {
    exit;
}

require PUN_ROOT . 'lang/' . $GLOBALS['pun_user']['language'] . '/fileup.php';


// Makes sure the mod is properly configured.
function check_mod_config()
{
    global $pun_config;

    if (!isset($pun_config['file_upload_path'])) {
        return false;
    }

    if (!is_dir(PUN_ROOT . $pun_config['file_upload_path'])) {
        return false;
    }

    if (!is_writable(PUN_ROOT . $pun_config['file_upload_path'])) {
        return false;
    }

    return true;
}


// Gets the extension of a file. This can either be a filename, or a full path to a file.
function get_file_extension($filename)
{
    $filename = basename($filename);
    if (($p = strrpos($filename, '.')) === false) {
        return $filename;
    } else {
        return substr($filename, $p + 1);
    }
}


// Is given file an image. Check by extension only.
function is_image_filename($filename)
{
    global $pun_config;
    return in_array(strtolower(get_file_extension($filename)), explode(',', $pun_config['file_image_ext']));
}

/*
********************************************************************************
** Returns the contents of a directory (including files and subdirectories.
********************************************************************************
*/
function get_dir_contents($dir)
{
    $contents = array();

    if (!is_dir($dir)) {
        return $contents;
    }

    $dh = opendir($dir);
    while (false !== ($file = readdir($dh))) {
        if ($file[0] == '.') {
            continue;
        }
        $contents[] = $file;
    }
    closedir($dh);

    natsort($contents);

    return $contents;
}

/*
********************************************************************************
** Returns number of files there are in a directory.
********************************************************************************
*/
function get_dir_file_count($dir)
{
    return sizeof(get_dir_contents($dir));
}


/*
********************************************************************************
** Used to check the upload directory for any problems.
********************************************************************************
*/
function show_problems()
{
    global $pun_config;

    $log = array();

    $files_dir = PUN_ROOT . $pun_config['file_upload_path'];
    $thumb_dir = PUN_ROOT . $pun_config['file_thumb_path'];

    if (!is_dir($files_dir)) {
        $log[] = 'Upload directory not found';
    } elseif (!is_writable($files_dir)) {
        $log[] = 'Upload directory not writable';
    }

    if (!is_dir($thumb_dir)) {
        $log[] = 'Thumbnails directory not found';
    } elseif (!is_writable($thumb_dir)) {
        $log[] = 'Thumbnails directory not writable';
    }

    if (!sizeof($log)) {
        $log[] = 'No problems Found!';
    }

    return $log;
}

/*
********************************************************************************
** Deletes any files that do not belong to a valid post.
** Delete broken links to missing files.
********************************************************************************
*/
function delete_orphans()
{
    global $pun_config, $db;

    $log = array();

    if (!check_mod_config()) {
        $log[] = 'File Upload is not configured correctly!';
        return $log;
    }

// collect files
    $files_dir = $pun_config['file_upload_path'];
    $files = get_dir_contents(PUN_ROOT . $files_dir);
    for ($i = 0; $i < sizeof($files); $i++) {
        $files[$i] = $files_dir . $files[$i];
    }

    $result = $db->query('SELECT a.id, a.post_id, a.location, p.id AS pid FROM ' . $db->prefix . 'attachments AS a LEFT JOIN ' . $db->prefix . 'posts AS p ON a.post_id=p.id') or error('Unable to execute query', __FILE__, __LINE__, $db->error());

// check every record
    while ($attachment = $db->fetch_assoc($result)) {
// missing post for attachment
        if (!$attachment['pid']) {
            $log[] = 'Attachment #' . $attachment['id'] . ': No related post - Deleted';
            $db->query('DELETE FROM ' . $db->prefix . 'attachments WHERE id=' . $attachment['id']) or error('Unable delete attachment(s)', __FILE__, __LINE__, $db->error());
        } else {
            $idx = array_search($attachment['location'], $files);

// file not exists but record in 'attachments' is
            if ($idx === false) {
                $log[] = 'File "' . $attachment['location'] . '" not found. Attathment #' . $attachment['id'] . ' from post #' . $attachment['post_id'] . ' will removed';
                $db->query('DELETE FROM ' . $db->prefix . 'attachments WHERE id=' . $attachment['id']) or error('Unable delete attachment(s)', __FILE__, __LINE__, $db->error());
            } else {
// allright, this file attached well. remove them from list
                array_splice($files, $idx, 1);
            }
        }
    }

// delete all orhpaned files in upload folder
    while (sizeof($files) > 0) {
        $file = $files[0];
        if ($file != 'index.html' && $file != '.htaccess') {
            $log[] = 'File "' . $file . '": No related record - Deleted';
            unlink($file);
        }
        array_splice($files, 0, 1);
    }

    if (!sizeof($log)) {
        $log[] = 'No problems Found!';
    }

    return $log;
}


/*
********************************************************************************
** Deletes any files that do not belong to a valid post.
** Delete broken links to missing files.
********************************************************************************
*/
function delete_all_thumbnails()
{
    global $pun_config;

    $log = array();

    if (!check_mod_config()) {
        $log[] = 'File Upload is not configured correctly!';
        return $log;
    }

    $thumbs_dir = $pun_config['file_thumb_path'];
    $thumbs = get_dir_contents(PUN_ROOT . $thumbs_dir);

    while (sizeof($thumbs) > 0) {
        $file = $thumbs[0];
        if ($file != 'index.html' && $file != '.htaccess' && $file[0] . $file[1] . $file[2] . $file[3] != 'err_') {
            $log[] = $file . ' - Deleted';
            unlink(PUN_ROOT . $thumbs_dir . $file);
        }
        array_splice($thumbs, 0, 1);
    }

    if (!sizeof($log)) {
        $log[] = 'No files Found!';
    }

    return $log;
}


/*
********************************************************************************
** Deletes any files that do not belong to a valid post.
** Delete broken links to missing files.
**
** NOTE: I'm too lazy to implements step-by-step fixing. For big forums
** time limit overflow is possible.
********************************************************************************
*/
function fix_user_counters()
{
    global $pun_config, $db;

    $counters = array();
    $result = $db->query('SELECT poster_id, count(*) FROM ' . $db->prefix . 'attachments GROUP BY poster_id') or error('Unable to count attachments.', __FILE__, __LINE__, $db->error());
    while ($row = $db->fetch_row($result)) {
        $counters[] = $row;
    }
    $db->free_result($result);

// erase counters for all users
    $db->query('UPDATE ' . $db->prefix . 'users SET num_files=0') or error('Unable to clear user counters', __FILE__, __LINE__, $db->error());

    $updated = 0;
    $sizeof = sizeof($counters);
    $log = array();
    $log[] = $sizeof . ' users has attachments';


    if ($sizeof) {
        for ($i = 0; $i < $sizeof; ++$i) {
            // update
            $db->query('UPDATE ' . $db->prefix . 'users SET num_files=' . $counters[$i][1] . ' WHERE id=' . $counters[$i][0]) or error('Unable to update users', __FILE__, __LINE__, $db->error());
        }
    }

    return $log;
}


/*
********************************************************************************
** Avoid to override existing files
********************************************************************************
*/
function generate_unique_filename($dir, $postfix)
{
    while (true) {
        $newname = md5(time() . 'Salt') . $postfix;
        if (!is_file($dir . $newname)) {
            return $newname;
        }
    }
}


/*
********************************************************************************
** Used when uploading images for a post.
********************************************************************************
*/
function process_uploaded_files($tid, $pid, &$total_uploaded)
{
    global $pun_config, $lang_common, $lang_fu, $db, $file_limit, $message;

    $result = null;
    if (!isset($_FILES['attach']['error']) or !check_mod_config()) {
        return $result;
    }

    $total_uploaded = 0;

    $dest = $pun_config['file_upload_path'];
    $thmb = $pun_config['file_thumb_path'];
    $allowed_ext = $pun_config['file_allowed_ext'];
    $allowed_ext = explode(',', $allowed_ext);
    $image_ext = $pun_config['file_image_ext'];
    $image_ext = explode(',', $image_ext);

// Upload all files
    $i = 0;
    $thumb_from = $thumb_to = array();

    foreach ($_FILES['attach']['error'] as $key => $error) {
        $i++;
        if ($error == UPLOAD_ERR_OK) {

            if ($file_limit <= 0) {
                break;
            }

// Grab the tmp file location, and the original file name
            $mime = $_FILES['attach']['type'][$key];
            $tmp_name = $_FILES['attach']['tmp_name'][$key];
// there are some PHP exploits with fake filenames
// file_exists() is not secure in this case!
            if (!is_uploaded_file($tmp_name)) {
                continue;
            }
            $orig_name = $_FILES['attach']['name'][$key];
            $size = filesize($tmp_name);
            $file_ext = strtolower(get_file_extension($orig_name));

// Skip files with banned extensions
            if (!in_array($file_ext, $allowed_ext) || !$file_ext) {
                $result .= $orig_name . ' ' . $lang_fu['Extension Banned'] . '.<br />';
                continue;
            }

// Skip files larger then max file size
            if ($size > $pun_config['file_max_size']) {
                $result .= $orig_name . ' ' . $lang_fu['Size Too Big'] . '.<br />';
                continue;
            }

            if (in_array($file_ext, $image_ext)) {
// Skip files that have larger then allowed dimensions
                list($width, $height, $type, $attr) = getimagesize($tmp_name);
                if (!$width || !$height) {
                    $result .= $orig_name . ' ' . $lang_fu['Not Image'] . '.<br />';
                    continue;
                }
                if ($width > $pun_config['file_max_width'] || $height > $pun_config['file_max_height']) {
                    $result .= $orig_name . ' ' . $lang_fu['Dim Too Big'] . '.<br />';
                    continue;
                }
                $dim = $width . 'x' . $height;
            } else {
                $dim = null;
            }

// save file to upload directory
            $store_name = generate_unique_filename(PUN_ROOT . $dest, '.ext');
            move_uploaded_file($tmp_name, PUN_ROOT . $dest . $store_name);
            chmod($dest . $store_name, 0666);

// NOTE: post author and attachment author may differ (if attach in edit)
            $attach_poster = $GLOBALS['pun_user']['id'];
            $now = time();
            $db->query('INSERT INTO ' . $db->prefix . 'attachments (poster_id, topic_id, post_id, uploaded, filename, mime, location, size, image_dim) VALUES (\'' . $attach_poster . '\', \'' . $tid . '\', \'' . $pid . '\', ' . $now . ', \'' . $db->escape($orig_name) . '\', \'' . $db->escape($mime) . '\', \'' . $db->escape($dest . $store_name) . '\', \'' . $size . '\', \'' . $dim . '\')') or error('Unable to insert attachment record into database.', __FILE__, __LINE__, $db->error());
            $aid = $db->insert_id();

            $thumb_from[] = '::thumb$' . $i . '::';
            $thumb_to[] = '::thumb' . $aid . '::';

            $total_uploaded++;
            $file_limit--;
        } else {
            switch ($error) {
                case UPLOAD_ERR_INI_SIZE:
                    $result .= 'File #' . $i . ' - ERROR: exceeds the upload_max_filesize<br />';
                    break;
                case UPLOAD_ERR_FORM_SIZE:
                    $result .= 'File #' . $i . ' - ERROR: exceeds the form MAX_FILE_SIZE<br />';
                    break;
                case UPLOAD_ERR_PARTIAL:
                    $result .= 'File #' . $i . ' - ERROR: partially uploaded<br />';
                    break;
                case UPLOAD_ERR_NO_FILE:
// no file specified in input field
                    break;
                default:
                    $result .= 'File #' . $i . ' - ERROR: ' . $error . '<br />';
                    break;
            }
        }
    }

    if ($total_uploaded) {
        $result .= '<br />' . $lang_fu['Uploaded'] . ' ' . $total_uploaded . ' ' . $lang_fu['files'] . '<br /><br />';
    }

// translate #i to ::thumbNN::
    if (strpos($message, '::thumb$') !== false) {
        $message = str_replace($thumb_from, $thumb_to, $message);
        $db->query('UPDATE ' . $db->prefix . 'posts SET message=\'' . $db->escape($message) . '\' WHERE id=' . $pid) or error('Unable to update post', __FILE__, __LINE__, $db->error());
    }

    return $result;
}

/*
********************************************************************************
** Deletes files during a post edit (if the user marks any images for deletion)
********************************************************************************
*/
function process_deleted_files($pid, &$total_deleted)
{
    global $pun_config, $db, $lang_fu, $file_limit;

    $result = '';

    if (!isset($_POST['delete_image']) || !check_mod_config()) {
        return $result;
    }

    $aid_list = implode(',', $_POST['delete_image']);
    $thumb_dir = PUN_ROOT . $pun_config['file_thumb_path'];
    $thumb_files = get_dir_contents($thumb_dir);

// check post_id to prevent hack
    $result_attach = $db->query('SELECT af.id, af.location FROM ' . $db->prefix . 'attachments AS af WHERE af.post_id=' . $pid . ' AND af.id IN (' . $aid_list . ')') or error('Unable to fetch attachments to delete', __FILE__, __LINE__, $db->error());
    $aid_list = array();

    $total_deleted = 0;
    while (list($aid, $location) = $db->fetch_row($result_attach)) {
        $aid_list[] = $aid;
        // Remove attachment
        unlink($location);
        // Remove all it's thumbnails
        foreach ($thumb_files as $thumb_file) {
            if (preg_match('/^' . $aid . '(-[0-9x]*)?\.jpg$/i', $thumb_file)) {
                unlink(PUN_ROOT . $pun_config['file_thumb_path'] . $thumb_file);
            }
        }
    }

    if ($aid_list) {
        $aid_list = implode(',', $aid_list);
        $db->query('DELETE FROM ' . $db->prefix . 'attachments WHERE id IN (' . $aid_list . ')') or error('Unable delete attachment(s)', __FILE__, __LINE__, $db->error());
        $file_limit++;
        $total_deleted = sizeof($aid_list);
    }

    if ($total_deleted) {
        $result .= '<br />' . $lang_fu['Deleted'] . ' ' . $total_deleted . ' ' . $lang_fu['files'] . '<br /><br />';
    }

    return $result;
}

/*
********************************************************************************
** Deletes all images related to a post.
********************************************************************************
*/
function delete_files($pid)
{
    global $pun_config, $db;

    if (!check_mod_config()) {
        return;
    }

    $thumb_dir = PUN_ROOT . $pun_config['file_thumb_path'];
    $thumb_files = get_dir_contents($thumb_dir);

    $result_attach = $db->query('SELECT af.id, af.location FROM ' . $db->prefix . 'attachments AS af WHERE af.post_id=' . $pid) or error('Unable to fetch attachments to delete', __FILE__, __LINE__, $db->error());

    while (list($aid, $location) = $db->fetch_row($result_attach)) {
// Remove attachment
        unlink($location);
// Remove all it's thumbnails
        foreach ($thumb_files as $thumb_file) {
            if (preg_match('/^' . $aid . '(-[0-9x]*)?\.jpg$/i', $thumb_file)) {
                unlink($thumb_dir . $thumb_file);
            }
        }
    }
    $db->query('DELETE FROM ' . $db->prefix . 'attachments WHERE post_id=' . $pid) or error('Unable delete attachment(s)', __FILE__, __LINE__, $db->error());
}


//
// Delete all attachments linked to posts
// in: $post_ids - one post_id or comma-separated list of post_ids
//
function delete_post_attachments($post_ids)
{
    global $db;

    if (strpos($post_ids, ',') === false) {
        $result = $db->query('SELECT id, poster_id, location FROM ' . $db->prefix . 'attachments WHERE post_id=' . $post_ids) or error('Unable to fetch attachments', __FILE__, __LINE__, $db->error());
    } else {
        $result = $db->query('SELECT id, poster_id, location FROM ' . $db->prefix . 'attachments WHERE post_id IN(' . $post_ids . ')') or error('Unable to fetch attachments', __FILE__, __LINE__, $db->error());
    }

    if ($db->num_rows($result)) {
        $att_ids = '';
        $poster_ids = array();
        $thumb_dir = PUN_ROOT . $GLOBALS['pun_config']['file_thumb_path'];
        $thumb_files = get_dir_contents($thumb_dir);

        while ($row = $db->fetch_assoc($result)) {
            $att_ids .= ($att_ids) ? ',' . $row['id'] : $row['id'];
            if (isset($poster_ids[$row['poster_id']])) {
                $poster_ids[$row['poster_id']]++;
            } else {
                $poster_ids[$row['poster_id']] = 1;
            }

// Delete file and all it's possible thumbnails
            unlink(PUN_ROOT . $row['location']);
            foreach ($thumb_files as $thumb_file) {
                if (preg_match('/^' . $row['id'] . '-[0-9x]+.*/i', $thumb_file)) {
                    unlink($thumb_dir . $thumb_file);
                }
            }
        }
        $db->free_result($result);

        if ($att_ids) {
// Delete attachment records
            $db->query('DELETE FROM ' . $db->prefix . 'attachments WHERE id IN(' . $att_ids . ')') or error('Unable to delete attachments', __FILE__, __LINE__, $db->error());
            foreach ($poster_ids as $poster_id => $num_files) {
// Fix user file counter
                $db->query('UPDATE ' . $db->prefix . 'users SET num_files=IF((num_files-' . $num_files . ')>0, num_files-' . $num_files . ', 0) WHERE id=' . $poster_id) or error('Unable to update users file counter', __FILE__, __LINE__, $db->error());
            }
        }
    }
}

/*
********************************************************************************
** Create thumbnail image and save result to file. Needs GD library!
** Thumbnail fits to size. JPEG used for output.
** When
** $do_cut==true - fit by short side and cut long side
** $do_cut==false - fit into box w/o any cuts
********************************************************************************
*/
function create_thumbnail($orig_fname, $thum_fname, $thumb_width = 100, $thumb_height = 100, $do_cut = false)
{
    $rgb = 0xFFFFFF;
    $quality = 80;
    $size = @getimagesize($orig_fname);
    $src_x = $src_y = 0;

    if ($size === false) {
        return false;
    }

    $format = strtolower(substr($size['mime'], strpos($size['mime'], '/') + 1));
    $icfunc = 'imagecreatefrom' . $format;
    if (!function_exists($icfunc)) {
        return false;
    }

    $orig_img = $icfunc($orig_fname);
    if (($size[0] <= $thumb_width) && ($size[1] <= $thumb_height)) {
// use original size
        $width = $size[0];
        $height = $size[1];
    } else {
        $width = $thumb_width;
        $height = $thumb_height;

// calculate fit ratio
        $ratio_width = $size[0] / $thumb_width;
        $ratio_height = $size[1] / $thumb_height;

        if ($ratio_width < $ratio_height) {
            if ($do_cut) {
                $src_y = ($size[1] - $thumb_height * $ratio_width) / 2;
                $size[1] = $thumb_height * $ratio_width;
            } else {
                $width = $size[0] / $ratio_height;
                $height = $thumb_height;
            }
        } else {
            if ($do_cut) {
                $src_x = ($size[0] - $thumb_width * $ratio_height) / 2;
                $size[0] = $thumb_width * $ratio_height;
            } else {
                $width = $thumb_width;
                $height = $size[1] / $ratio_width;
            }
        }
    }

    $thum_img = imagecreatetruecolor($width, $height);
    imagefill($thum_img, 0, 0, $rgb);
    imagecopyresampled($thum_img, $orig_img, 0, 0, $src_x, $src_y, $width, $height, $size[0], $size[1]);

    imagejpeg($thum_img, $thum_fname, $quality);
    flush();
    imagedestroy($orig_img);
    imagedestroy($thum_img);
    return true;
}


/*
********************************************************************************
** Wrappers for create_thumbnail()
**
** require_thumb_name - Compose name for thumbnail.
** require_thumb - Compose name for thumbnail and create it.
** handle_thumb_tag - Make sure that thumbnail created at post pre-parse time
********************************************************************************
*/
function require_thumb_name($aid, $width = 100, $height = 100, $do_cut = false)
{
    global $pun_config;

    return $pun_config['file_thumb_path'] . $aid . '-' . $width . 'x' . $height . (($do_cut) ? '-cut' : '') . '.jpg';
}

function require_thumb($aid, $location, $width = 100, $height = 100, $do_cut = false)
{
    global $pun_config;

    if ($aid && is_file($location)) {
        $thum_fname = require_thumb_name($aid, $width, $height, $do_cut);
        if (!is_file($thum_fname)) {
            @set_time_limit(10);
// if any error, create_thumbnail() will not call again, but
// thumbnail will be copy of err_thumb.gif
            copy(PUN_ROOT . $pun_config['file_thumb_path'] . 'err_thumb.gif', $thum_fname);
            create_thumbnail($location, $thum_fname, $width, $height, $do_cut);
        }
        return $thum_fname;
    } else {
        return $pun_config['file_thumb_path'] . 'err_none.gif';
    }
}


function handle_thumb_tag($aid)
{
    $db = $GLOBALS['db'];

    $result = $db->query('SELECT location FROM ' . $db->prefix . 'attachments WHERE id=' . $aid) or error('Unable to fetch attachment', __FILE__, __LINE__, $db->error());
    if ($db->num_rows($result)) {
        $width = $GLOBALS['pun_config']['file_preview_width'];
        $height = $GLOBALS['pun_config']['file_preview_height'];
        list($location) = $db->fetch_row($result);
        require_thumb($aid, $location, $width, $height);
    }

    return '::thumb' . $aid . '::'; // really tag not translated :)
}
